<?php

namespace App\Http\Controllers\Api;

use App\Enums\AssetSourceType;
use App\Enums\AssetStatus;
use App\Enums\AvailableStatus;
use App\Enums\ConvertStatus;
use App\Enums\TokenLockType;
use App\Enums\TokenStatus;
use App\Enums\TransType;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\ConvertDemand;
use App\Models\ConvertFormula;
use App\Models\ConvertRecord;
use App\Models\ConvertToken;
use App\Models\FrontendUser;
use App\Models\ProductToken;
use App\Services\WalletService;
use Illuminate\Support\Arr;

class ConvertController extends Controller
{
    /** @var WalletService */
    private $walletService;

    /**
     * ConvertController constructor.
     */
    public function __construct(WalletService $walletService)
    {
        $this->walletService = $walletService;
    }

    /** 查询合成公式列表 */
    public function listFormulas()
    {
        $formulas = ConvertFormula::query()
            ->where('status', AvailableStatus::ENABLED)
            ->where('agent_id', request()->agent_id)
            ->with(['demands', 'target'])
            ->orderByDesc('id')
            ->get();
        $result = [];
        foreach ($formulas as $formula) {
            $target = $formula->target;
            $show = false;
            $demandContractAddress = request()->demand_contract_address;
            if (!$demandContractAddress) {
                $show = true;
            } else {
                foreach ($formula->demands as $demand) {
                    if ($demand->contract_address === $demandContractAddress) {
                        $show = true;

                        break;
                    }
                }
            }
            if ($show) {
                $result[] = [
                    'id' => $formula->id,
                    'convert_name' => $formula->name,
                    'start_time' => $formula->start_time ?: '',
                    'end_time' => $formula->end_time ?: '',
                    'inventory' => $target->available_tokens()->count(),
                    'issue_count' => $target->issue_count,
                    'limit' => $formula->limit,
                    'target_product_id' => $formula->target_product_id,
                    'target_name' => $target->name,
                    'target_cover_image' => $target->logo_url,
                ];
            }
        }

        return $result;
    }

    /** 查询公式详情 */
    public function getFormula($id)
    {
        $formula = $this->findFormulaOrFail($id);
        $target = $formula->target;
        $result = [
            'target_product_id' => $formula->target_product_id,
            'target_name' => $target->name,
            'target_cover_image' => $target->logo_url,
            'inventory' => $target->available_tokens()->count(),
            'issue_count' => $target->issue_count,
            'start_time' => $formula->start_time ?: '',
            'end_time' => $formula->end_time ?: '',
            'limit' => $formula->limit,
            'description' => $target->description,
        ];
        $demands = $formula->demands()->with(['product'])->get();
        $contractAddressList = Arr::pluck($demands, 'contract_address');
        $userAssetCounts = $this->getUserAssetCount($contractAddressList);

        /** @var ConvertDemand $demand */
        foreach ($demands as $demand) {
            $product = $demand->product;
            $result['demands'][] = [
                'product_id' => $demand->product_id,
                'product_name' => $product->name,
                'cover_image' => $product->logo_url,
                'required_quantity' => $demand->quantity,
                'hold_quantity' => Arr::get($userAssetCounts, $demand->contract_address, 0),
            ];
        }

        return $result;
    }

    /** 合成 */
    public function convert($id)
    {
        request()->validate([
            'count' => 'required',
        ]);

        /** @var FrontendUser $user */
        $user = auth()->user();
        $formula = $this->findFormulaOrFail($id);
        $target = $formula->target;
        $convertCount = request()->count;
        if ($convertCount <= 0) {
            throw new ApiException('合成数量必须大于0');
        }
        // 检查合成目标库存
        if ($target->available_tokens()->count() < $convertCount) {
            throw new ApiException('合成目标库存不足');
        }
        //检查起止时间
        if ($formula->start_time && now()->lt($formula->start_time)) {
            throw new ApiException('合成活动未开始');
        }
        if ($formula->end_time && now()->gt($formula->end_time)) {
            throw new ApiException('合成活动已结束');
        }
        //检查合成数量限制
        $recordCount = ConvertRecord::query()
            ->where('convert_formula_id', $formula->id)
            ->where('user_id', $user->id)
            ->sum('convert_count');
        if ($formula->limit > 0 && $recordCount + $convertCount > $formula->limit) {
            throw new ApiException('超出可合成的最大数量');
        }
        $demands = $formula->demands()->with(['product'])->get();
        // 检查资产数量（未锁定，未转售）
        $userAssetCounts = $this->getUserAssetCount(Arr::pluck($demands, 'contract_address'));

        /** @var ConvertDemand $demand */
        foreach ($demands as $demand) {
            $userCount = Arr::get($userAssetCounts, $demand->contract_address, 0);
            if ($demand->quantity * $convertCount > $userCount) {
                throw new ApiException(sprintf('资产【%s】拥有数量不足', $demand->product->name));
            }
        }
        $targetTokens = $target->available_tokens()->limit($convertCount)->get();

        $record = new ConvertRecord();
        $record->user_id = $user->id;
        $record->convert_formula_id = $formula->id;
        $record->convert_count = $convertCount;
        $record->status = ConvertStatus::PROCESSING;
        $record->remark = '';

        \DB::transaction(function () use ($record, $convertCount, $targetTokens, $demands) {
            // 记录合成记录
            $record->save();
            // 产品token标记为锁定
            $convertTokenValues = [];
            foreach ($targetTokens as $targetToken) {
                $convertTokenValues[] = [
                    'created_at' => now(),
                    'updated_at' => now(),
                    'convert_record_id' => $record->id,
                    'token_id' => $targetToken->token_id,
                    'trans_type' => TransType::CONVERT_IN,
                ];
            }
            ProductToken::query()->whereIn('id', Arr::pluck($targetTokens, 'id'))->update([
                'lock_type' => TokenLockType::CONVERT,
                'lock_refer_id' => $record->id,
                'status' => TokenStatus::LOCKED,
            ]);

            // 资产标记为待回收
            foreach ($demands as $demand) {
                $demandAssets = $this->myAssetQuery()
                    ->where('contract_address', $demand->contract_address)
                    ->limit($convertCount * $demand->quantity)
                    ->get();
                // 资产标记为待回收
                foreach ($demandAssets as $demandAsset) {
                    $convertTokenValues[] = [
                        'created_at' => now(),
                        'updated_at' => now(),
                        'convert_record_id' => $record->id,
                        'token_id' => $demandAsset->token_id,
                        'trans_type' => TransType::CONVERT_RECOVERY,
                    ];
                }
                Asset::query()->whereIn('id', Arr::pluck($demandAssets, 'id'))
                    ->update([
                        'asset_status' => AssetStatus::WAIT_RECOVERY,
                    ]);
            }
            ConvertToken::query()->insert($convertTokenValues);
        });

        try {
            // 目标资产转账
            $transResult = $this->walletService->transfer(
                '',
                $target->contract_address,
                Arr::pluck($targetTokens, 'token_id'),
                $user->wallet_address
            );
            // 更新token状态
            foreach ($targetTokens as $token) {
                $transHash = Arr::get($transResult, $token->token_id, 'trans_hash_not_return');
                $token->status = TokenStatus::SOLD;
                $token->save();

                // 保存资产信息
                $asset = new Asset();
                $asset->user_id = auth()->id();
                $asset->source_type = AssetSourceType::CONVERT;
                $asset->source_id = $record->id;
                $asset->contract_address = $target->contract_address;
                $asset->token_id = $token->token_id;
                $asset->trans_hash = $transHash;
                $asset->asset_status = AssetStatus::HOLD;
                $asset->save();
            }
            $record->status = ConvertStatus::SUCCESS;
            $record->remark = '';
            $record->save();
        } catch (\Exception $e) {
            $record->status = ConvertStatus::FAIL;
            $record->remark = $e->getMessage();
            $record->save();
        }
    }

    private function getUserAssetCount($contractAddressList)
    {
        return $this->myAssetQuery()
            ->whereIn('contract_address', $contractAddressList)
            ->select(['contract_address', \DB::raw('count(*) as count')])
            ->groupBy(['contract_address'])
            ->get()
            ->mapWithKeys(function (Asset $asset) {
                return [$asset->contract_address => $asset->count];
            });
    }

    private function myAssetQuery()
    {
        return Asset::query()
            ->where('user_id', auth()->id())
            ->where('asset_status', AssetStatus::HOLD);
    }

    private function findFormulaOrFail($id)
    {
        $formula = ConvertFormula::query()
            ->where('status', AvailableStatus::ENABLED)
            ->where('agent_id', request()->agent_id)
            ->find($id);
        if (!$formula) {
            throw new ApiException('无效的合成ID');
        }

        return $formula;
    }
}
